function ModuleVisa(doc, container, width, height, user, date, options) {
  this._doc = doc;
  this._container = container;
  this._width = width;
  this._height = height;
  this._user = user;
  this._date = date;
  this._options = options;

  this._createElements();
};

ModuleVisa.prototype._createElements = function() {
  this._gui = new Visa(this._doc, this._container, this._width, this._height, this._user, this._date, this._options)._gui;
  var popupBox = (this._options && this._options.popupBox) ? this._options.popupBox : null;
  var callbackFunc = (this._options && this._options.callbackFunc) ? this._options.callbackFunc : function() {};
  //Preset
  var months = MiscUtils.months();
  var years = MiscUtils.years();
  this._gui.medical_month.appendChild(new Option(''));
  this._gui.pcc_month.appendChild(new Option(''));
  for (var i = 0, il = months.length; i < il; i++) {
    var tmp = months[i];
    this._gui.medical_month.appendChild(new Option(tmp));
    this._gui.pcc_month.appendChild(new Option(tmp));
  }
  this._gui.medical_year.appendChild(new Option(''));
  this._gui.pcc_year.appendChild(new Option(''));
  for (var i = 0, il = years.length; i < il; i++) {
    var tmp = years[i];
    this._gui.medical_year.appendChild(new Option(tmp));
    this._gui.pcc_year.appendChild(new Option(tmp));
  }
  this._gui.visa_result.appendChild(new Option(''));
  var results = VisaResult.array();
  for (var i = 0, il = results.length; i < il; i++) {
    var tmp = results[i];
    this._gui.visa_result.appendChild(new Option(tmp));
  }
  //Object
  var id = (this._options && this._options.id) ? this._options.id : null;
  var visa = (this._options && this._options.object) ? this._options.object : pz_visa.instance();
  if (!id && !visa.id) {
    this._disable(true);
  } else {
    this._disable(false);
  }
  if (!visa.id) {
    visa.client_id = id;
    visa.visa_date_expiry_current = 0;
    visa.visa_appl_date_submit = 0;
    visa.visa_date_issue = 0;
    visa.visa_date_expiry_new = 0;
    visa.visa_date_pickup = 0;
  }
  //Display
  this._gui.client_num.value = visa.client_num;
  this._gui.visa_date_expiry_current.value = (visa.visa_date_expiry_current == 0) ? '' : SimpleDate.format(SimpleDate.fromStamp(visa.visa_date_expiry_current, false), false);
  this._gui.record_conviction.value = visa.record_conviction;
  this._gui.record_refuse.value = visa.record_refuse;
  
  this._gui.visa_appl_type.value = visa.visa_appl_type;
  this._gui.visa_appl_date_submit.value = (visa.visa_appl_date_submit == 0) ? '' : SimpleDate.format(SimpleDate.fromStamp(visa.visa_appl_date_submit, false), false);
  for (var i = 0, il = this._gui.medical_month.options.length; i < il; i++) {
    var tmp = this._gui.medical_month.options[i];
    if (tmp.text == visa.medical_month) {
      this._gui.medical_month.selectedIndex = i;
    }
  }
  for (var i = 0, il = this._gui.medical_year.options.length; i < il; i++) {
    var tmp = this._gui.medical_year.options[i];
    if (tmp.text == visa.medical_year) {
      this._gui.medical_year.selectedIndex = i;
    }
  }
  for (var i = 0, il = this._gui.pcc_month.options.length; i < il; i++) {
    var tmp = this._gui.pcc_month.options[i];
    if (tmp.text == visa.pcc_month) {
      this._gui.pcc_month.selectedIndex = i;
    }
  }
  for (var i = 0, il = this._gui.pcc_year.options.length; i < il; i++) {
    var tmp = this._gui.pcc_year.options[i];
    if (tmp.text == visa.pcc_year) {
      this._gui.pcc_year.selectedIndex = i;
    }
  }
  
  this._gui.record_process.value = visa.record_process;
  var _self = this;
  this._gui.record_process_split.onclick = function() { visa.record_process = 'Staff: ' + _self._user.account + ' Date: ' + SimpleDate.format(SimpleDate.fromDate(new Date(), true), true) + '\n\nContent: ' + ((visa.record_process == '') ? '' : ('\n\n--------------------------------------------------------------------------------------\n\n') + visa.record_process);
                                                        _self._gui.record_process.value = visa.record_process;
                                                      };
  
  this._gui.visa_appl_num.value = visa.visa_appl_num;
  this._gui.visa_date_issue.value = (visa.visa_date_issue == 0) ? '' : SimpleDate.format(SimpleDate.fromStamp(visa.visa_date_issue, false), false);
  this._gui.visa_date_expiry_new.value = (visa.visa_date_expiry_new == 0) ? '' : SimpleDate.format(SimpleDate.fromStamp(visa.visa_date_expiry_new, false), false);
  
  for (var i = 0, il = this._gui.visa_result.options.length; i < il; i++) {
    var tmp = this._gui.visa_result.options[i];
    if (tmp.text == visa.visa_result) {
      this._gui.visa_result.selectedIndex = i;
    }
  }
  this._gui.visa_date_pickup.value = (visa.visa_date_pickup == 0) ? '' : SimpleDate.format(SimpleDate.fromStamp(visa.visa_date_pickup, false), false);
  //Event
  this._gui.client_num.onchange = function() { visa.client_num = this.value; };
  this._gui.visa_date_expiry_current.onclick = function() { _self._selectDate(this, visa.visa_date_expiry_current, function(sd) { visa.visa_date_expiry_current = SimpleDate.toStamp(sd, false); }, false); };
  this._gui.record_conviction.onchange = function() { visa.record_conviction = this.value; };
  this._gui.record_refuse.onchange = function() { visa.record_refuse = this.value; };
  
  this._gui.visa_appl_type.onchange = function() { visa.visa_appl_type = this.value; };
  this._gui.visa_appl_date_submit.onclick = function() { _self._selectDate(this, visa.visa_appl_date_submit, function(sd) { visa.visa_appl_date_submit = SimpleDate.toStamp(sd, false); }, false); };
  this._gui.medical_month.onchange = function() { visa.medical_month = this.options[this.selectedIndex].text; };
  this._gui.medical_year.onchange = function() { visa.medical_year = this.options[this.selectedIndex].text; };
  this._gui.pcc_month.onchange = function() { visa.pcc_month = this.options[this.selectedIndex].text; };
  this._gui.pcc_year.onchange = function() { visa.pcc_year = this.options[this.selectedIndex].text; };
  
  //Upload Docs
  if (id || visa.id) {
    var unique_doc = 'doc_';
    var browse_doc = this._doc.createElement('span');
    var cancel_doc = this._doc.createElement('span');
    browse_doc.id = unique_doc + UploadUtils._defaultButtonPlaceholderId;
    cancel_doc.id = unique_doc + UploadUtils._defaultCancelButtonId;
    this._gui.docs.appendChild(browse_doc);
    this._gui.docs.appendChild(cancel_doc);
    
    var pos_doc = DOMUtils.findPos(browse_doc);
    var up_doc = null;
    var initFunc = function() { var div = document.createElement('div');
                                var p = new ModulePopupBox();
                                p._open(div, { pos: [pos_doc[0] - 468, pos_doc[1] - 260], width: 468, t: '', doc: document }); 
                                up_doc = new ModuleUpload(document, div, 468, 260, _self._user, _self._date, { u: unique_doc, callbackFunc: function() { _self._loadData.call(_self); }, popupBox: p });
                              };
    
    var uu_doc = new UploadUtils(unique_doc);
    uu_doc._fileTypes = '*.jpeg;*.jpg;*.png;*.gif;*.pdf;*.doc;*.docx';
    uu_doc._fileTypesDescription = 'Scanned Files';
    uu_doc._buttonImageUrl = 'image/upload/browse.png';
    uu_doc._buttonWidth = 94;
    uu_doc._buttonHeight = 29;
    uu_doc._createInstance(visa.client_id + '/' + visa.track_id + '/', { c: visa.client_id, v: visa.track_id, f: Constant.FILE_TYPE_DOC }, initFunc, function(file, result) { up_doc._callbackFunc(file, result); });
    
    var unique_visa = 'visa_';
    var browse_visa = this._doc.createElement('span');
    var cancel_visa = this._doc.createElement('span');
    browse_visa.id = unique_visa + UploadUtils._defaultButtonPlaceholderId;
    cancel_visa.id = unique_visa + UploadUtils._defaultCancelButtonId;
    this._gui.visa.appendChild(browse_visa);
    this._gui.visa.appendChild(cancel_visa);
    
    var pos_visa = DOMUtils.findPos(browse_visa);
    var up_visa = null;
    var initFunc = function() { var div = document.createElement('div');
                                var p = new ModulePopupBox();
                                p._open(div, { pos: [pos_visa[0] - 468, pos_visa[1] - 260], width: 468, t: '', doc: document }); 
                                up_visa = new ModuleUpload(document, div, 468, 260, _self._user, _self._date, { u: unique_visa, callbackFunc: function() { _self._loadData.call(_self); }, popupBox: p });
                              };
    
    var uu_visa = new UploadUtils(unique_visa);
    uu_visa._fileTypes = '*.jpeg;*.jpg;*.png;*.gif;*.pdf;*.doc;*.docx';
    uu_visa._fileTypesDescription = 'Scanned Files';
    uu_visa._buttonImageUrl = 'image/upload/browse.png';
    uu_visa._buttonWidth = 94;
    uu_visa._buttonHeight = 29;
    uu_visa._createInstance(visa.client_id + '/' + visa.track_id + '/', { c: visa.client_id, v: visa.track_id, f: Constant.FILE_TYPE_VISA }, initFunc, function(file, result) { up_visa._callbackFunc(file, result); });

    this._files = null;
    this._visa = visa;
    this._loadData();
  }
  
  this._gui.record_process.onchange = function() { visa.record_process = this.value; };
  
  this._gui.visa_appl_num.onchange = function() { visa.visa_appl_num = this.value; };
  this._gui.visa_date_issue.onclick = function() { _self._selectDate(this, visa.visa_date_issue, function(sd) { visa.visa_date_issue = SimpleDate.toStamp(sd, false); }, false); };
  this._gui.visa_date_expiry_new.onclick = function() { _self._selectDate(this, visa.visa_date_expiry_new, function(sd) { visa.visa_date_expiry_new = SimpleDate.toStamp(sd, false); }, false); };
  
  this._gui.visa_result.onchange = function() { visa.visa_result = this.options[this.selectedIndex].text; };
  this._gui.visa_date_pickup.onclick = function() { _self._selectDate(this, visa.visa_date_pickup, function(sd) { visa.visa_date_pickup = SimpleDate.toStamp(sd, false); }, false); };
  //Submit
  //Notice: pz_visa is operated by mysqlcustom.php
  this._gui.save.onclick = function() { new RequestUtils()._custom('visa', JSON.stringify([{ n: 'pz_visa', a: [visa], r: [] }]), function(result, params) { if (_self._options.mode == Constant.VISA_MODE_EXIST_POP || _self._options.mode == Constant.VISA_MODE_NEW_POP) { popupBox._close(); }; callbackFunc(); }, { pos: DOMUtils.findPos(this) });
                                      };
  this._gui.cancel.onclick = function() { popupBox._close(); callbackFunc(); };
};

ModuleVisa.prototype._loadData = function() {
  DOMUtils.removeChildElements(this._gui.docs_read);
  DOMUtils.removeChildElements(this._gui.visa_read);
  
  var tr, td, img;
  tr = this._gui.docs_read.insertRow(-1);
  td = tr.insertCell(-1);
  img = document.createElement('img');
  img.src = 'image/loading.gif';
  td.appendChild(img);
  
  tr = this._gui.visa_read.insertRow(-1);
  td = tr.insertCell(-1);
  img = document.createElement('img');
  img.src = 'image/loading.gif';
  td.appendChild(img);
  
  this._retrieveFiles();
};

ModuleVisa.prototype._verifyData = function() {
  if (this._files) {
    this._updateElements();
  }
};

ModuleVisa.prototype._retrieveFiles = function() {
  var _self = this;
  new RequestUtils()._read('pz_file', null, 'visa_track_id = \'' + this._visa.track_id + '\'', null, null, null, null, function(result, params) { _self._files = result.data;
                                                                                                                                                  _self._verifyData.call(_self);
                                                                                                                                                }, null);
};

ModuleVisa.prototype._updateElements = function() {
  DOMUtils.removeChildElements(this._gui.docs_read);
  DOMUtils.removeChildElements(this._gui.visa_read);
  
  var tr_doc, td_doc, tr_visa, td_visa, count_doc = 0, count_visa = 0, a, img, _self = this;
  for (var i = 0, il = this._files.length; i < il; i++) {
    var tmp = this._files[i];
    
    p = new ModulePopupBoxSimpleTip();
    a = document.createElement('a');
    a.href = '#';
    a._tmp = tmp;
    a.onmouseover = function() { var div = document.createElement('div');
                                 div.appendChild(document.createTextNode(this._tmp.name + ', ' + this._tmp.size / 1000 + 'KB'));
                                 var pos = DOMUtils.findPos(this)
                                 p._open(div, { pos: [pos[0] + 80, pos[1] + 75 ] });
                               };
    a.onmouseout = function() { p._close(); }; 
    a.onclick = function() { window.open('download.php?q=' + JSON.stringify({ i: this._tmp.id }), '_blank');
                             return false;
                           };
    a.appendChild(document.createTextNode(MiscUtils.filename(tmp.name, 9)));
    
    img = document.createElement('img');
    img.src = 'image/delete.png';
    img.style.padding = '0 0 0 5px';
    img.style.cursor = 'pointer';
    img._tmp = tmp;
    img.onclick = function() { new RequestUtils()._custom('delete', JSON.stringify({ files: [this._tmp] }), function(result, params) { _self._loadData.call(_self); }, { pos: DOMUtils.findPos(this) });
                             };
                             
    if (tmp.type == Constant.FILE_TYPE_DOC) {
      if ((count_doc % 5) == 0) {
        tr_doc = this._gui.docs_read.insertRow(-1);
      }
      td_doc = tr_doc.insertCell(-1);
      td_doc.appendChild(a);
      td_doc.appendChild(img);
      count_doc++;
    } else if (tmp.type == Constant.FILE_TYPE_VISA) {
      if ((count_visa % 5) == 0) {
        tr_visa = this._gui.visa_read.insertRow(-1);
      }
      td_visa = tr_visa.insertCell(-1);
      td_visa.appendChild(a);
      td_visa.appendChild(img);
      count_visa++;
    }
  }
};

ModuleVisa.prototype._selectDate = function(display, date, callbackFunc, showTime) {
  var div = document.createElement('div');
  var ds = new DateSelect((date == 0) ? null : SimpleDate.fromStamp(date, false), null, { container: div, showTime: showTime });
  ds._selectFunc = function(sd) { callbackFunc(sd);
                                  if (sd) { 
                                    display.value = SimpleDate.format(sd, showTime);
                                  } else {
                                    display.value = '';
                                  }
                                  popupBox._close();
                                };
  
  var popupBox = new ModulePopupBoxSimple();
  var pos = DOMUtils.findPos(display);
  popupBox._open(div, { pos: [pos[0], pos[1] + 85] });
};

ModuleVisa.prototype._disable = function(enabled) {
  this._gui.client_num.disabled = enabled;
  this._gui.visa_date_expiry_current.disabled = enabled;
  this._gui.record_conviction.disabled = enabled;
  this._gui.record_refuse.disabled = enabled;

  this._gui.visa_appl_type.disabled = enabled;
  this._gui.visa_appl_date_submit.disabled = enabled;
  this._gui.medical_month.disabled = enabled;
  this._gui.medical_year.disabled = enabled;
  this._gui.pcc_month.disabled = enabled;
  this._gui.pcc_year.disabled = enabled;

  this._gui.record_process_split.disabled = enabled;
  this._gui.record_process.disabled = enabled;

  this._gui.visa_appl_num.disabled = enabled;
  this._gui.visa_result.disabled = enabled;
  this._gui.visa_date_issue.disabled = enabled;
  this._gui.visa_date_expiry_new.disabled = enabled;
  this._gui.visa_date_pickup.disabled = enabled;

  this._gui.save.disabled = enabled;
  this._gui.cancel.disabled = enabled;
};